package com.ada.transfer.factory;

import com.ada.transfer.anno.*;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 工程类，使用反射技术生成对象
 */
public class BeanFactory {
    /**
     * 1.读取解析xml，通过反射技术实例化队形并且存储待用，map集合
     * 2.对外提供获取实例对象的接口，根据id获取
     */
    private static HashMap<String, Object> map = new HashMap<>(); //存储对象 <id,class>

    //提供获取bean的接口
    public static Object getBean(String id) {
        return map.get(id);
    }

    public static Object getBeanByType(Class T) {
        return map.get(T.getSimpleName().toLowerCase());
    }


    static {
        // 读取解析xml，通过反射技术实例化队形并且存储待用，map集合
        //加载xml
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        //解析
        try {
            Document document = new SAXReader().read(resourceAsStream);
            Element rootElement = document.getRootElement();
            doAnnoRegister(rootElement);//注解配置bean解析
            doXmlRegister(rootElement);//xml配置bean解析
            doParseProperty(rootElement);//xml依赖配置解析，注解依赖配置解析
            doParseTransAction();//事务解析
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 注解解析
     *
     * @param rootElement
     */
    public static void doAnnoRegister(Element rootElement) throws IOException, IllegalAccessException, InstantiationException {
        List<Element> scanList = rootElement.selectNodes("//component-scan");
        for (Element element : scanList) {
            String scan = element.attributeValue("base-package");
            List<Class<?>> classes = getClasses(scan);
            for (Class<?> aClass : classes) {
                if (aClass.isAnnotation()) {
                    continue;
                }
                String key = null;
                if (aClass.isAnnotationPresent(Component.class)) {
                    key = aClass.getSimpleName().toLowerCase();
                }
                Service serviceAnno = aClass.getAnnotation(Service.class);
                if (serviceAnno != null) {
                    if (!serviceAnno.value().isEmpty()) {
                        key = serviceAnno.value();
                    } else {
                        key = aClass.getSimpleName().toLowerCase();
                    }
                }
                Repository repositoryAnno = aClass.getAnnotation(Repository.class);
                if (repositoryAnno != null) {
                    if (!repositoryAnno.value().isEmpty()) {
                        key = repositoryAnno.value();
                    } else {
                        key = aClass.getSimpleName().toLowerCase();
                    }
                }
                if (key == null || key.isEmpty()) {
                    continue;
                }
                Object resultInstance = aClass.newInstance();
                map.put(key, resultInstance);
                if (aClass.getInterfaces().length > 0) {
                    for (Class<?> anInterface : aClass.getInterfaces()) {
                        map.put(anInterface.getSimpleName().toLowerCase(), resultInstance);
                    }
                }
            }
        }

    }

    /**
     * xml解析
     *
     * @param rootElement
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws InvocationTargetException
     */
    public static void doXmlRegister(Element rootElement) throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException {
        List<Element> list = rootElement.selectNodes("//bean");
        //处理每个bean元素，获取到对应的id和class属性
        for (int i = 0; i < list.size(); i++) {
            Element element = list.get(i);
            String id = element.attributeValue("id");
            String clazz = element.attributeValue("class");//类的全限定名
            Class<?> aClass = Class.forName(clazz);
            Object o = aClass.newInstance();
            //存入map
            map.put(id, o);
        }

    }

    /**
     * 解析依赖
     * 解析引用参数 AutoWired
     * 解析xml里的参数
     */
    public static void doParseProperty(Element rootElement) throws InvocationTargetException, IllegalAccessException {
        //实例化完成之后维护对象依赖关系，检查哪些对象需要传值进去
        //有property的bean
        List<Element> propertyList = rootElement.selectNodes("//property");
        //解析并且获取父元素
        for (int i = 0; i < propertyList.size(); i++) {
            Element element = propertyList.get(i);
            String name = element.attributeValue("name");
            String ref = element.attributeValue("ref");
            //找到父元素
            String parentId = element.getParent().attributeValue("id");
            Object parentObject = map.get(parentId);
            //通过父元素反射，遍历父对象的所有方法，找到 set+name
            Method[] methods = parentObject.getClass().getMethods();
            for (int j = 0; j < methods.length; j++) {
                Method method = methods[j];
                if (method.getName().equalsIgnoreCase("set" + name)) { //该方法就是我们要找的方法
                    method.invoke(parentObject, map.get(ref));
                }
            }
            //把处理之后的parentObj重新放入map
            map.put(parentId, parentObject);
        }

        /**
         * 解析注解依赖
         */
        for (Map.Entry<String, Object> stringObjectEntry : map.entrySet()) {
            Object parentObject = stringObjectEntry.getValue();
            Field[] fields = parentObject.getClass().getDeclaredFields();
            for (Field field : fields) {
                if (field.getAnnotation(Autowired.class) != null) {
                    field.setAccessible(true);
                    //byName先尝试去找
                    Object fieldObject = map.get(field.getType().getSimpleName().toLowerCase());
                    if (fieldObject != null) {
                        field.set(parentObject, fieldObject);
                    } else {//byType尝试去找
                        for (Object value : map.values()) {
                            if (value.getClass() == field.getClass()) {
                                field.set(parentObject, value);
                                break;
                            }
                        }
                    }
                }
            }
            //把处理之后的parentObj重新放入map
            map.put(stringObjectEntry.getKey(), parentObject);
        }
    }

    /**
     * 解析事务
     */
    public static void doParseTransAction() {
        ProxyFactory proxyFactory = (ProxyFactory) getBeanByType(ProxyFactory.class);
        for (Map.Entry<String, Object> stringObjectEntry : map.entrySet()) {
            if (stringObjectEntry.getClass().isAnnotationPresent(Transactional.class)) {
                if (stringObjectEntry.getClass().getInterfaces().length > 0) {
                    Object proxy = proxyFactory.getJdkProxy(stringObjectEntry);
                    map.put(stringObjectEntry.getKey(), proxy);
                    for (Class<?> anInterface : stringObjectEntry.getClass().getInterfaces()) {
                        map.put(anInterface.getSimpleName().toLowerCase(), proxy);
                    }
                } else {
                    map.put(stringObjectEntry.getKey(), proxyFactory.getCglibProxy(stringObjectEntry));

                }
            }
        }
    }

    /**
     * 通过目录获取对应的class
     *
     * @param packageName
     * @return
     * @throws IOException
     */
    public static List<Class<?>> getClasses(String packageName) throws IOException {
        // 第一个class类的集合
        List<Class<?>> classes = new ArrayList<Class<?>>();

        // 获取包的名字 并进行替换
        String packageDirName = packageName.replace('.', '/');

        Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(packageDirName);

        while (resources.hasMoreElements()) {
            URL url = resources.nextElement();
            String protocol = url.getProtocol();
            if ("file".equals(protocol)) {
                // 获取包的物理路径
                String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                findAllClassInPackAge(packageName, filePath, classes);
            }
        }
        return classes;
    }

    /**
     * 找到对应的目录下的class，放到集合里
     *
     * @param filePath
     * @param classes
     */
    public static void findAllClassInPackAge(String packageName, String filePath, List<Class<?>> classes) {
        File dir = new File(filePath);
        // 如果不存在或者 也不是目录就直接返回
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        // 如果存在 就获取包下的所有文件 包括目录
        File[] dirfiles = dir.listFiles(new FileFilter() {
            // 目录要找子文件，直接class结尾的就是需要的文件
            public boolean accept(File file) {
                return file.isDirectory() || file.getName().endsWith(".class");
            }
        });
        // 循环所有文件
        for (File file : dirfiles) {
            // 如果是目录 则继续扫描
            if (file.isDirectory()) {
                findAllClassInPackAge(packageName + "." + file.getName(), file.getAbsolutePath(),
                        classes);
            } else {
                // 如果是java类文件 去掉后面的.class 只留下类名
                String className = file.getName().substring(0, file.getName().length() - 6);
                try {
                    // 添加到集合中去
                    classes.add(Class.forName(packageName + '.' + className));
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
